{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# OSMnx features demo\n",
    "\n",
    "Author: [Geoff Boeing](https://geoffboeing.com/)\n",
    "\n",
    "Model street networks and other geospatial features anywhere in the world from OpenStreetMap then analyze and visualize them.\n",
    "\n",
    "More info:\n",
    "\n",
    "  - [Documentation](https://osmnx.readthedocs.io/)\n",
    "  - [Journal article and citation info](https://geoffboeing.com/publications/osmnx-paper/)\n",
    "  - [Code repository](https://github.com/gboeing/osmnx)\n",
    "  - [Examples gallery](https://github.com/gboeing/osmnx-examples)\n",
    "  \n",
    "This notebook provides a quick tour of some of OSMnx's key features including how to:\n",
    "\n",
    "  - download/model street networks\n",
    "  - calculate stats\n",
    "  - visualize centrality\n",
    "  - impute speeds/travel times and calculate shortest paths\n",
    "  - attach and visualize elevation data and edge grades\n",
    "  - download/model other infrastructure types\n",
    "  - download points of interest data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import networkx as nx\n",
    "import osmnx as ox\n",
    "\n",
    "ox.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Working with street networks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# download/model a street network for some city then visualize it\n",
    "G = ox.graph.graph_from_place(\"Piedmont, California, USA\", network_type=\"drive\")\n",
    "fig, ax = ox.plot.plot_graph(G)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OSMnx geocodes the query \"Piedmont, California, USA\" to retrieve the place boundaries of that city from the Nominatim API, retrieves the drivable street network data within those boundaries from the Overpass API, constructs a graph model, then simplifies/corrects its topology such that nodes represent intersections and dead-ends and edges represent the street segments linking them. All of this is discussed in detail in the documentation and these examples.\n",
    "\n",
    "OSMnx models all networks as NetworkX `MultiDiGraph` objects. You can convert to:\n",
    "  - undirected MultiGraphs\n",
    "  - DiGraphs without (possible) parallel edges\n",
    "  - GeoPandas node/edge GeoDataFrames\n",
    "\n",
    "Note that converting to an undirected MultiGraph is really only meant for use cases where a function or algorithm only accepts a MultiGraph argument. If you just want a fully bidirectional graph (such as for a walking network), just configure the `settings` module’s `bidirectional_network_types` before creating your graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get a fully bidirection network (as a MultiDiGraph)\n",
    "ox.settings.bidirectional_network_types += \"drive\"\n",
    "G = ox.graph.graph_from_place(\"Piedmont, California, USA\", network_type=\"drive\")\n",
    "\n",
    "# convert your MultiDiGraph to an undirected MultiGraph\n",
    "M = ox.convert.to_undirected(G)\n",
    "\n",
    "# convert your MultiDiGraph to a DiGraph without parallel edges\n",
    "D = ox.convert.to_digraph(G)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# you can convert your graph to node and edge GeoPandas GeoDataFrames\n",
    "gdf_nodes, gdf_edges = ox.convert.graph_to_gdfs(G)\n",
    "gdf_nodes.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf_edges.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can create a graph from node/edge GeoDataFrames, as long as gdf_nodes is indexed by osmid and gdf_edges is multi-indexed by u, v, key (following normal MultiDiGraph structure). This allows you to load graph node/edge ShapeFiles or GeoPackage layers as GeoDataFrames then convert to a MultiDiGraph for graph analytics."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# convert node/edge GeoPandas GeoDataFrames to a NetworkX MultiDiGraph\n",
    "G2 = ox.convert.graph_from_gdfs(gdf_nodes, gdf_edges, graph_attrs=G.graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic street network stats"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# what sized area does our network cover in square meters?\n",
    "G_proj = ox.projection.project_graph(G)\n",
    "nodes_proj = ox.convert.graph_to_gdfs(G_proj, edges=False)\n",
    "graph_area_m = nodes_proj.union_all().convex_hull.area\n",
    "graph_area_m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# show some basic stats about the network\n",
    "ox.stats.basic_stats(G_proj, area=graph_area_m, clean_int_tol=15)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "stats documentation: https://osmnx.readthedocs.io/en/stable/osmnx.html#module-osmnx.stats"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# save graph to disk as geopackage (for GIS) or graphml file (for gephi etc)\n",
    "ox.io.save_graph_geopackage(G, filepath=\"./data/mynetwork.gpkg\")\n",
    "ox.io.save_graphml(G, filepath=\"./data/mynetwork.graphml\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualize street centrality\n",
    "\n",
    "Here we plot the street network and color its edges (streets) by their relative closeness centrality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# convert graph to line graph so edges become nodes and vice versa\n",
    "edge_centrality = nx.closeness_centrality(nx.line_graph(G))\n",
    "nx.set_edge_attributes(G, edge_centrality, \"edge_centrality\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# color edges in original graph with closeness centralities from line graph\n",
    "ec = ox.plot.get_edge_colors_by_attr(G, \"edge_centrality\", cmap=\"inferno\")\n",
    "fig, ax = ox.plot.plot_graph(G, edge_color=ec, edge_linewidth=2, node_size=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Routing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# impute missing edge speeds and calculate edge travel times with the speed module\n",
    "G = ox.routing.add_edge_speeds(G)\n",
    "G = ox.routing.add_edge_travel_times(G)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get the nearest network nodes to two lat/lng points with the distance module\n",
    "orig = ox.distance.nearest_nodes(G, X=-122.245846, Y=37.828903)\n",
    "dest = ox.distance.nearest_nodes(G, X=-122.215006, Y=37.812303)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# find the shortest path between nodes, minimizing travel time, then plot it\n",
    "route = ox.routing.shortest_path(G, orig, dest, weight=\"travel_time\")\n",
    "fig, ax = ox.plot.plot_graph_route(G, route, node_size=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# how long is our route in meters?\n",
    "edge_lengths = ox.routing.route_to_gdf(G, route)[\"length\"]\n",
    "round(sum(edge_lengths))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# how far is it between these two nodes as the crow flies?\n",
    "# use OSMnx's vectorized great-circle distance (haversine) function\n",
    "orig_x = G.nodes[orig][\"x\"]\n",
    "orig_y = G.nodes[orig][\"y\"]\n",
    "dest_x = G.nodes[dest][\"x\"]\n",
    "dest_y = G.nodes[dest][\"y\"]\n",
    "round(ox.distance.great_circle(orig_y, orig_x, dest_y, dest_x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can add elevation attributes to your graph's nodes automatically with the `elevation` module, using either local raster files or the Google Maps Elevation API."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# add elevation to nodes automatically, calculate edge grades, plot network\n",
    "# you need a google elevation api key to run this cell!\n",
    "try:\n",
    "    from keys import google_elevation_api_key\n",
    "\n",
    "    G = ox.elevation.add_node_elevations_google(G, api_key=google_elevation_api_key)\n",
    "    G = ox.elevation.add_edge_grades(G)\n",
    "    nc = ox.plot.get_node_colors_by_attr(G, \"elevation\", cmap=\"plasma\")\n",
    "    fig, ax = ox.plot.plot_graph(\n",
    "        G, node_color=nc, node_size=20, edge_linewidth=2, edge_color=\"#333\"\n",
    "    )\n",
    "except ImportError:\n",
    "    print(\"You need a google_elevation_api_key to run this cell.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nodes are colored from lowest elevation (dark blue) to highest (bright yellow).\n",
    "\n",
    "Example: create [elevation-based](12-node-elevations-edge-grades.ipynb) impedance functions to route around hills."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Get networks other ways\n",
    "\n",
    "make queries less ambiguous to help the geocoder out, if it's not finding what you're looking for"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# you can make query an unambiguous dict to help the geocoder find it\n",
    "place = {\"city\": \"San Francisco\", \"state\": \"California\", \"country\": \"USA\"}\n",
    "G = ox.graph.graph_from_place(place, network_type=\"drive\", truncate_by_edge=True)\n",
    "fig, ax = ox.plot.plot_graph(G, figsize=(10, 10), node_size=0, edge_color=\"y\", edge_linewidth=0.2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# you can get networks anywhere in the world\n",
    "G = ox.graph.graph_from_place(\"Sinalunga, Italy\", network_type=\"all\")\n",
    "fig, ax = ox.plot.plot_graph(G, node_size=0, edge_linewidth=0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# or get network by address, coordinates, bounding box, or any custom polygon\n",
    "# ...useful when OSM just doesn't already have a polygon for the place you want\n",
    "wurster_hall = (37.870605, -122.254830)\n",
    "one_mile = 1609  # meters\n",
    "G = ox.graph.graph_from_point(wurster_hall, dist=one_mile, network_type=\"drive\")\n",
    "fig, ax = ox.plot.plot_graph(G, node_size=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Examples of [getting networks](01-overview-osmnx.ipynb) by coordinates, bounding box, or any custom polygon shape."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Get other networked infrastructure types\n",
    "\n",
    "...like rail or electric grids or even the canals of Venice and Amsterdam, using the `custom_filter` parameter: [see more examples](08-custom-filters-infrastructure.ipynb)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get NY subway rail network\n",
    "G = ox.graph.graph_from_place(\n",
    "    \"New York, New York, USA\",\n",
    "    retain_all=False,\n",
    "    truncate_by_edge=True,\n",
    "    simplify=True,\n",
    "    custom_filter='[\"railway\"~\"subway\"]',\n",
    ")\n",
    "\n",
    "fig, ax = ox.plot.plot_graph(G, node_size=0, edge_color=\"w\", edge_linewidth=0.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Get any geospatial features' geometries and attributes\n",
    "\n",
    "Use the `features` module to download any OSM features, such as local amenities, points of interest, or building footprints, and turn them into a GeoDataFrame: [see docs](https://osmnx.readthedocs.io/en/stable/osmnx.html#module-osmnx.features). For more usage examples of downloading geospatial features from OSM, see [this notebook](16-download-osm-geospatial-features.ipynb)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get all building footprints in some neighborhood\n",
    "place = \"Chinatown, San Francisco, California\"\n",
    "tags = {\"building\": True}\n",
    "gdf = ox.features.features_from_place(place, tags)\n",
    "gdf.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = ox.plot.plot_footprints(gdf, figsize=(3, 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See the other notebooks for more examples of visualization with OSMnx."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get all parks and bus stops in some neighborhood\n",
    "tags = {\"leisure\": \"park\", \"highway\": \"bus_stop\"}\n",
    "gdf = ox.features.features_from_place(place, tags)\n",
    "gdf.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (ox)",
   "language": "python",
   "name": "ox"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
